// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/big_endian.h"
#include "base/time/tick_clock.h"
#include "media/cast/net/rtcp/receiver_rtcp_session.h"
#include "media/cast/net/rtcp/rtcp_builder.h"
#include "media/cast/net/rtcp/rtcp_defines.h"
#include "media/cast/net/rtcp/rtcp_utility.h"

namespace media {
namespace cast {

    ReceiverRtcpSession::ReceiverRtcpSession(base::TickClock* clock,
        uint32_t local_ssrc,
        uint32_t remote_ssrc)
        : clock_(clock)
        , local_ssrc_(local_ssrc)
        , remote_ssrc_(remote_ssrc)
        , last_report_truncated_ntp_(0)
        , local_clock_ahead_by_(ClockDriftSmoother::GetDefaultTimeConstant())
        , lip_sync_ntp_timestamp_(0)
        , parser_(local_ssrc, remote_ssrc)
    {
    }

    ReceiverRtcpSession::~ReceiverRtcpSession() { }

    bool ReceiverRtcpSession::IncomingRtcpPacket(const uint8_t* data,
        size_t length)
    {
        // Check if this is a valid RTCP packet.
        if (!IsRtcpPacket(data, length)) {
            VLOG(1) << "Rtcp@" << this << "::IncomingRtcpPacket() -- "
                    << "Received an invalid (non-RTCP?) packet.";
            return false;
        }

        // Check if this packet is to us.
        uint32_t ssrc_of_sender = GetSsrcOfSender(data, length);
        if (ssrc_of_sender != remote_ssrc_) {
            return false;
        }

        // Parse this packet.
        base::BigEndianReader reader(reinterpret_cast<const char*>(data), length);
        if (parser_.Parse(&reader)) {
            if (parser_.has_sender_report()) {
                OnReceivedNtp(parser_.sender_report().ntp_seconds,
                    parser_.sender_report().ntp_fraction);
                OnReceivedLipSyncInfo(parser_.sender_report().rtp_timestamp,
                    parser_.sender_report().ntp_seconds,
                    parser_.sender_report().ntp_fraction);
            }
        }
        return true;
    }

    void ReceiverRtcpSession::OnReceivedNtp(uint32_t ntp_seconds,
        uint32_t ntp_fraction)
    {
        last_report_truncated_ntp_ = ConvertToNtpDiff(ntp_seconds, ntp_fraction);

        const base::TimeTicks now = clock_->NowTicks();
        time_last_report_received_ = now;

        // TODO(miu): This clock offset calculation does not account for packet
        // transit time over the network.  End2EndTest.EvilNetwork confirms that this
        // contributes a very significant source of error here.  Determine whether
        // RTT should be factored-in, and how that changes the rest of the
        // calculation.
        const base::TimeDelta measured_offset = now - ConvertNtpToTimeTicks(ntp_seconds, ntp_fraction);
        local_clock_ahead_by_.Update(now, measured_offset);
        if (measured_offset < local_clock_ahead_by_.Current()) {
            // Logically, the minimum offset between the clocks has to be the correct
            // one.  For example, the time it took to transmit the current report may
            // have been lower than usual, and so some of the error introduced by the
            // transmission time can be eliminated.
            local_clock_ahead_by_.Reset(now, measured_offset);
        }
        VLOG(1) << "Local clock is ahead of the remote clock by: "
                << "measured=" << measured_offset.InMicroseconds() << " usec, "
                << "filtered=" << local_clock_ahead_by_.Current().InMicroseconds()
                << " usec.";
    }

    void ReceiverRtcpSession::OnReceivedLipSyncInfo(RtpTimeTicks rtp_timestamp,
        uint32_t ntp_seconds,
        uint32_t ntp_fraction)
    {
        if (ntp_seconds == 0) {
            NOTREACHED();
            return;
        }
        lip_sync_rtp_timestamp_ = rtp_timestamp;
        lip_sync_ntp_timestamp_ = (static_cast<uint64_t>(ntp_seconds) << 32) | ntp_fraction;
    }

    bool ReceiverRtcpSession::GetLatestLipSyncTimes(
        RtpTimeTicks* rtp_timestamp,
        base::TimeTicks* reference_time) const
    {
        if (!lip_sync_ntp_timestamp_)
            return false;

        const base::TimeTicks local_reference_time = ConvertNtpToTimeTicks(
                                                         static_cast<uint32_t>(lip_sync_ntp_timestamp_ >> 32),
                                                         static_cast<uint32_t>(lip_sync_ntp_timestamp_))
            + local_clock_ahead_by_.Current();

        // Sanity-check: Getting regular lip sync updates?
        DCHECK((clock_->NowTicks() - local_reference_time) < base::TimeDelta::FromMinutes(1));

        *rtp_timestamp = lip_sync_rtp_timestamp_;
        *reference_time = local_reference_time;
        return true;
    }

} // namespace cast
} // namespace media
